﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using MvcContrib.TestHelper;
using MvcContrib.TestHelper.Fakes;
using ProgressTracker.Account;
using ProgressTracker.Account.Messages;
using ProgressTracker.Account.Services;
using ProgressTracker.Email.Contracts;
using ProgressTracker.Email.Messages;
using ProgressTracker.WebUI.Controllers;
using ProgressTracker.WebUI.Infrastructure;
using ProgressTracker.WebUI.Models.Account;
using System.Linq;
using System.Web.Mvc;

namespace ProgressTracker.Tests.Controllers
{
    [TestClass]
    public class AccountControllerTests
    {
        private Mock<IAccountService> _accountService;
        private Mock<IAuthentication> _authentication;
        private Mock<IEmailService> _emailService;

        private CreateModel _testCreateModel;
        private LoginModel _testLoginModel;
        private UserLogin _testUser;
        private ChangePasswordModel _testChangePasswordModel;

        [TestInitialize]
        public void Initialize()
        {
            _accountService = new Mock<IAccountService>();
            _authentication = new Mock<IAuthentication>();
            _emailService = new Mock<IEmailService>();

            _testCreateModel = new CreateModel
            {
                ConfirmPassword = "test",
                Password = "test",
                Email = "test@test.com"
            };

            _testLoginModel = new LoginModel
            {
                Email = "test@test.com",
                Password = "test",
                RememberUser = false
            };

            _testUser = new UserLogin
            {
                Email = "test@test.com"
            };

            _testChangePasswordModel = new ChangePasswordModel
            {
                OldPassword = "Test",
                NewPassword = "TestNew",
                ConfirmPassword = "TestNew",
                UserEmail = _testUser.Email
            };
        }

        [TestMethod]
        public void Test_Create_Get_Returns_View()
        {
            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Create();

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_Create_Post_Redirects_To_Index_On_Success()
        {
            _accountService.Setup(x => x.CreateAccount(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new AccountCreateResult
                {
                    Message = "No Account - Test",
                    ResultCode = AccountResultCode.Success
                });

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Create(_testCreateModel);

            result.AssertActionRedirect().ToController("Home").ToAction("Index");
        }

        [TestMethod]
        public void Test_Create_Post_Returns_Create_Get_On_Fail_Create()
        {
            _accountService.Setup(x => x.CreateAccount(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new AccountCreateResult
                {
                    Message = "No Account - Test",
                    ResultCode = AccountResultCode.FailedToCreate
                });


            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Create(_testCreateModel);

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_Create_Post_Adds_Error_On_Fail_Create()
        {
            var message = "No Account - Test";

            _accountService.Setup(x => x.CreateAccount(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new AccountCreateResult
                {
                    Message = message,
                    ResultCode = AccountResultCode.FailedToCreate
                });

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Create(_testCreateModel);

            var viewResult = result as ViewResult;
            Assert.AreEqual(viewResult.ViewData.ModelState["CreateError"].Errors.Single().ErrorMessage, message);
        }

        [TestMethod]
        public void Test_Create_Post_Returns_Create_Get_On_User_Exists()
        {
            _accountService.Setup(x => x.CreateAccount(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new AccountCreateResult
                {
                    Message = "No Account - Test",
                    ResultCode = AccountResultCode.UserAlreadyExists
                });


            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Create(_testCreateModel);

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_Create_Post_Adds_Error_On_User_Exists()
        {
            var message = "No Account - Test";

            _accountService.Setup(x => x.CreateAccount(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new AccountCreateResult
                {
                    Message = message,
                    ResultCode = AccountResultCode.UserAlreadyExists
                });

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Create(_testCreateModel);

            var viewResult = result as ViewResult;
            Assert.AreEqual(viewResult.ViewData.ModelState["CreateError"].Errors.Single().ErrorMessage, message);
        }

        [TestMethod]
        public void Test_Login_Get_Returns_View()
        {
            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Login();

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_Login_Post_Redirects_To_Home_Index_On_Success()
        {
            _accountService.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new LoginResult
                {
                    Message = "No Login - Test",
                    ResultCode = LoginResultCode.Success,
                    User = _testUser
                });

            _authentication.Setup(x => x.Authenticate(It.IsAny<string>(), It.IsAny<bool>()));

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Login(_testLoginModel);

            result.AssertActionRedirect().ToController("Home").ToAction("Index");
        }

        [TestMethod]
        public void Test_Login_Post_Authentications_User_On_Success()
        {
            _accountService.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new LoginResult
                {
                    Message = "No Login - Test",
                    ResultCode = LoginResultCode.Success,
                    User = _testUser
                });

            _authentication.Setup(x => x.Authenticate(It.IsAny<string>(), It.IsAny<bool>())).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Login(_testLoginModel);

            _authentication.Verify();
        }

        [TestMethod]
        public void Test_Login_Post_Redirects_To_Create_On_Account_Does_Not_Exist()
        {
            _accountService.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new LoginResult
                {
                    Message = "No Login - Test",
                    ResultCode = LoginResultCode.AccountDoesNotExist
                });

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Login(_testLoginModel);

            result.AssertActionRedirect().ToAction("Create");
        }

        [TestMethod]
        public void Test_Login_Post_Renders_View_On_Password_Mismatch()
        {
            _accountService.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new LoginResult
                {
                    Message = "No Login - Test",
                    ResultCode = LoginResultCode.PasswordDoesNotMatch
                });

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Login(_testLoginModel);

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_Login_Post_Adds_Login_Error_On_Password_Mismatch()
        {
            var message = "No Login - Test";

            _accountService.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>())).Returns(
                new LoginResult
                {
                    Message = message,
                    ResultCode = LoginResultCode.PasswordDoesNotMatch
                });

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Login(_testLoginModel) as ViewResult;

            Assert.AreEqual(result.ViewData.ModelState["LoginError"].Errors.Single().ErrorMessage, message);
        }

        [TestMethod]
        public void Test_Logout_Renders_View()
        {
            _authentication.Setup(x => x.SignOut()).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Logout();

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_Logout_Logs_User_Out()
        {
            _authentication.Setup(x => x.SignOut()).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.Logout();

            _authentication.Verify();
        }

        [TestMethod]
        public void Test_ResetPassword_Get_Renders_View()
        {
            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ResetPassword();

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_ResetPassword_Post_Redirects_Back_To_ResetPassword_View_If_Account_Does_Not_Exist()
        {
            _accountService.Setup(x => x.AccountExists(_testUser.Email)).Returns(false).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ResetPassword(_testUser.Email);

            result.AssertViewRendered();
            _accountService.Verify();
        }

        [TestMethod]
        public void Test_ResetPassword_Post_Does_Not_Change_Password_If_Account_Does_Not_Exist()
        {
            _accountService.Setup(x => x.AccountExists(_testUser.Email)).Returns(false);

            _accountService.Setup(x => x.ResetPassword(It.IsAny<string>())).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ResetPassword(_testUser.Email);

            _accountService.Verify(x => x.ResetPassword(It.IsAny<string>()), Times.Never);
        }

        [TestMethod]
        public void Test_ResetPassword_Post_Resets_Password_If_Account_Exists()
        {
            _accountService.Setup(x => x.AccountExists(_testUser.Email)).Returns(true);

            _accountService.Setup(x => x.ResetPassword(_testUser.Email)).Returns(new ResetPasswordResult
                {
                    NewPassword ="ResetTest"
                }).Verifiable();

            _emailService.Setup(x => x.SendEmail(It.IsAny<SendEmailRequest>()));

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ResetPassword(_testUser.Email);

            _accountService.Verify();
        }

        [TestMethod]
        public void Test_ResetPassword_Post_Sends_Email_To_User_With_New_Password()
        {
            _accountService.Setup(x => x.AccountExists(_testUser.Email)).Returns(true);

            _accountService.Setup(x => x.ResetPassword(_testUser.Email)).Returns(new ResetPasswordResult
            {
                NewPassword = "Test"
            });

            _emailService.Setup(x => x.SendEmail(It.Is<SendEmailRequest>(
                s => s.ToAddress == _testUser.Email))).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ResetPassword(_testUser.Email);

            _emailService.Verify();
        }

        [TestMethod]
        public void Test_ResetPassword_Post_Redirects_To_Home_On_Successful_Reset()
        {
            _accountService.Setup(x => x.AccountExists(_testUser.Email)).Returns(true);

            _accountService.Setup(x => x.ResetPassword(_testUser.Email)).Returns(new ResetPasswordResult
            {
                NewPassword = "Test"
            });

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ResetPassword(_testUser.Email);

            result.AssertActionRedirect().ToController("Home").ToAction("Index");
        }

        [TestMethod]
        public void Test_ChangePassword_Get_Renders_View()
        {
            //need to generate fake http context and fake principal to utilize 'User' variable.
            var user = new FakePrincipal(new FakeIdentity(_testUser.Email), null);

            var builder = new TestControllerBuilder();
            builder.HttpContext.User = user;

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            builder.InitializeController(accountController);

            var result = accountController.ChangePassword();

            result.AssertViewRendered();
        }

        [TestMethod]
        public void Test_ChangePassword_Post_Redirects_Back_To_ChangePassword_View_If_Old_Password_Is_Not_Correct()
        {
            _accountService.Setup(x => x.Login(_testChangePasswordModel.UserEmail, _testChangePasswordModel.OldPassword)).Returns(new LoginResult
            {
                ResultCode = LoginResultCode.PasswordDoesNotMatch
            }).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ChangePassword(_testChangePasswordModel);

            result.AssertViewRendered();
            _accountService.Verify();
        }

        [TestMethod]
        public void Test_ChangePassword_Post_Does_Not_Change_Password_If_Old_Password_Is_Not_Correct()
        {
            _accountService.Setup(x => x.Login(_testChangePasswordModel.UserEmail, _testChangePasswordModel.OldPassword)).Returns(new LoginResult
                {
                    ResultCode = LoginResultCode.PasswordDoesNotMatch
                });

            _accountService.Setup(x => x.ChangePassword(_testChangePasswordModel.UserEmail, _testChangePasswordModel.NewPassword)).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ChangePassword(_testChangePasswordModel);

            _accountService.Verify(x => x.ChangePassword(_testChangePasswordModel.UserEmail, _testChangePasswordModel.NewPassword), Times.Never);
        }

        [TestMethod]
        public void Test_ChangePassword_Post_Changes_Password_If_Old_Password_Is_Correct()
        {
            _accountService.Setup(x => x.Login(_testChangePasswordModel.UserEmail, _testChangePasswordModel.OldPassword)).Returns(new LoginResult
            {
                ResultCode = LoginResultCode.Success
            });

            _accountService.Setup(x => x.ChangePassword(_testChangePasswordModel.UserEmail, _testChangePasswordModel.NewPassword)).Verifiable();

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ChangePassword(_testChangePasswordModel);

            _accountService.Verify();
        }

        [TestMethod]
        public void Test_ChangePassword_Post_Redirects_To_Home_On_Successful_Password_Change()
        {
            _accountService.Setup(x => x.Login(_testChangePasswordModel.UserEmail, _testChangePasswordModel.OldPassword)).Returns(new LoginResult
            {
                ResultCode = LoginResultCode.Success
            });

            _accountService.Setup(x => x.ChangePassword(_testChangePasswordModel.UserEmail, _testChangePasswordModel.NewPassword));

            var accountController = new AccountController(_accountService.Object, _authentication.Object, _emailService.Object);
            var result = accountController.ChangePassword(_testChangePasswordModel);

            result.AssertActionRedirect().ToController("Home").ToAction("Index");
        }
    }
}
